{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.677531Z",
     "start_time": "2025-01-17T11:47:17.669792Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 19
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 准备数据\n",
    "对非数据集数据进行处理，将其转换为张量。\n"
   ],
   "id": "b2ca68305d4f1003"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.757324Z",
     "start_time": "2025-01-17T11:47:17.745150Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home=\"./data\")\n",
    "\n",
    "print(type(housing))  # <class'sklearn.utils.Bunch'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.data.shape)  # (20640, 8)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.data))  # <class 'numpy.ndarray'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.target.shape)  # (20640,)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.target))  # <class 'numpy.ndarray'>"
   ],
   "id": "179320fc991c0d9e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sklearn.utils._bunch.Bunch'>\n",
      "--------------------------------------------------\n",
      "(20640, 8)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n",
      "--------------------------------------------------\n",
      "(20640,)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.768484Z",
     "start_time": "2025-01-17T11:47:17.758336Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 将数据集分为训练集和测试集\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7, test_size=0.25)\n",
    "\n",
    "# 将训练集分为训练集和验证集\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "# 把3个数据集都放到字典中\n",
    "dataset_maps = {\n",
    "    \"train\": (x_train, y_train),  # 训练集\n",
    "    \"valid\": (x_valid, y_valid),  # 验证集\n",
    "    \"test\": (x_test, y_test)  # 测试集\n",
    "}\n"
   ],
   "id": "6438d1e42b76dee3",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.777017Z",
     "start_time": "2025-01-17T11:47:17.769486Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()  #标准化\n",
    "\n",
    "# fit和fit_transform的区别，\n",
    "# fit是计算均值和方差，fit_transform是先fit，然后transform\n",
    "scaler.fit(x_train)"
   ],
   "id": "31716505cd2977d0",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-2 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-2 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-2 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-2 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-2 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-2 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-2 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" checked><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 构建数据集\n",
    "\n",
    "我们认为最后两维作为位置信息要单独处理，故制作数据集如下"
   ],
   "id": "16087ce2514248b7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.786345Z",
     "start_time": "2025-01-17T11:47:17.778020Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "# 因为最终目的是为了将数据分批，所以要继承自Dataset\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, dataset_maps, mode=\"train\"):\n",
    "        # x,y都是ndarray类型\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        # from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "        self.y = torch.from_numpy(self.y.reshape(-1, 1)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "\n",
    "    def __len__(self):  # 必须写\n",
    "        return len(self.x)  # 返回数据集的长度，0维的size\n",
    "\n",
    "    def __getitem__(self, index):  # 必须写\n",
    "        # idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return (self.x[index],self.x[index][-2:]), self.y[index]\n",
    "        #返回的是一个元组，元组里面是两个元素，第一个元素是一个元组，第二个元素是一个数。self.x[idx][-2:]代表取最后两个元素\n",
    "\n",
    "\n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(dataset_maps, mode=\"train\")\n",
    "valid_ds = HousingDataset(dataset_maps, mode=\"valid\")\n",
    "test_ds = HousingDataset(dataset_maps, mode=\"test\")"
   ],
   "id": "6983b17b0a43ddbf",
   "outputs": [],
   "execution_count": 23
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.819908Z",
     "start_time": "2025-01-17T11:47:17.812353Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0]  # 第一个样本的x和y，都是张量",
   "id": "32eb5ab0d2d15cec",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n",
       "  tensor([-0.5898, -0.0824])),\n",
       " tensor([3.2260]))"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 24
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.837658Z",
     "start_time": "2025-01-17T11:47:17.830915Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][0]",
   "id": "5b70d99fe2daee9e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n",
       " tensor([-0.5898, -0.0824]))"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 25
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.850805Z",
     "start_time": "2025-01-17T11:47:17.844661Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][1]",
   "id": "2cac8b93a241b2cc",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([3.2260])"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 26
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## DataLoader",
   "id": "ac48caac2c0b7d1b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.882264Z",
     "start_time": "2025-01-17T11:47:17.877815Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "# 放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "# 过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "batch_size = 8  #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "e4913bc7597b3d04",
   "outputs": [],
   "execution_count": 27
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "d35e08caf2939a19"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.900746Z",
     "start_time": "2025-01-17T11:47:17.894271Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=(8,2)):\n",
    "        super().__init__()\n",
    "        # 全连接层\n",
    "        # Deep 部分：用于学习输入特征的高阶非线性关系。\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim[1], 30),  # 30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30),  # 30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # 输出层\n",
    "        # 将 Deep 部分的输出（30 维）和原始输入特征（input_dim 维）拼接后，映射到 1 维输出。\n",
    "        # Wide 部分：直接使用原始输入特征，保留低阶特征信息。\n",
    "        # 输出层：将 Deep 部分的输出和 Wide 部分的输入拼接后，通过一个全连接层生成最终输出。\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + +input_dim[0], 1)\n",
    "\n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        # 初始化权重\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x_wide, x_deep):\n",
    "        # 前向传播\n",
    "        deep_output = self.deep(x_deep)\n",
    "        # print(deep_output.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        # 将两个张量（deep_output 和 x）在指定的维度（dim=1）上进行拼接（concatenation）\n",
    "        concat=torch.cat([x_wide,deep_output], dim=1)\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        return logits\n",
    "    \n"
   ],
   "id": "95800ab9c21dfe0e",
   "outputs": [],
   "execution_count": 28
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.916697Z",
     "start_time": "2025-01-17T11:47:17.911751Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 早停\n",
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -10000  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ],
   "id": "dafde4c7bc482222",
   "outputs": [],
   "execution_count": 29
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.952364Z",
     "start_time": "2025-01-17T11:47:17.946707Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 评估函数\n",
    "# 因为是回归问题，所以计算准确率无意义\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    for (datas_deep, datas_wide), labels in data_loader:\n",
    "        datas_deep = datas_deep.to(device)\n",
    "        datas_wide = datas_wide.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas_deep,datas_wide)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "    return np.mean(loss_list)  # # 返回验证集平均损失和准确率"
   ],
   "id": "fee145430e7ea5df",
   "outputs": [],
   "execution_count": 30
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:47:17.971081Z",
     "start_time": "2025-01-17T11:47:17.962371Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练函数\n",
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for (datas_deep, datas_wide), labels in train_loader:\n",
    "                datas_deep = datas_deep.to(device)\n",
    "                datas_wide = datas_wide.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas_deep,datas_wide)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ],
   "id": "3d36c3eaec6fc763",
   "outputs": [],
   "execution_count": 31
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:48:21.585827Z",
     "start_time": "2025-01-17T11:47:17.975085Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 20\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()  # 损失函数\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. 定义早停\n",
    "early_stop_callback = EarlyStopCallback(patience=10, \n",
    "                                        min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record_dict = training(model,\n",
    "                       train_loader,\n",
    "                       val_loader,\n",
    "                       epoch,\n",
    "                       loss_fct,\n",
    "                       optimizer,\n",
    "                       early_stop_callback=early_stop_callback,\n",
    "                       eval_step=len(train_loader)\n",
    "                       )"
   ],
   "id": "6fb98b8ab19337eb",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/29040 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "8b7f661646c945b9a3daeae2319e9cb6"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 32
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:48:21.593338Z",
     "start_time": "2025-01-17T11:48:21.587829Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"train\"][-5:]",
   "id": "acc7cd1cd573870",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.5269548296928406, 'step': 29035},\n",
       " {'loss': 0.7332636713981628, 'step': 29036},\n",
       " {'loss': 0.3348486125469208, 'step': 29037},\n",
       " {'loss': 0.5107153058052063, 'step': 29038},\n",
       " {'loss': 0.06217429041862488, 'step': 29039}]"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 33
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:48:21.600013Z",
     "start_time": "2025-01-17T11:48:21.594341Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"val\"][-15:]",
   "id": "172382f4d0c2e1a8",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.5268393295609262, 'step': 7260},\n",
       " {'loss': 0.5198896908516849, 'step': 8712},\n",
       " {'loss': 0.5250572204851538, 'step': 10164},\n",
       " {'loss': 0.5113243465889091, 'step': 11616},\n",
       " {'loss': 0.5067407745052098, 'step': 13068},\n",
       " {'loss': 0.5046663106272905, 'step': 14520},\n",
       " {'loss': 0.5040523353117433, 'step': 15972},\n",
       " {'loss': 0.5131409689668597, 'step': 17424},\n",
       " {'loss': 0.4972031364710001, 'step': 18876},\n",
       " {'loss': 0.5004118531980852, 'step': 20328},\n",
       " {'loss': 0.49610088309196154, 'step': 21780},\n",
       " {'loss': 0.5006247401191306, 'step': 23232},\n",
       " {'loss': 0.4879362218867033, 'step': 24684},\n",
       " {'loss': 0.48692609202347636, 'step': 26136},\n",
       " {'loss': 0.5126189041444031, 'step': 27588}]"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 34
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:48:21.713068Z",
     "start_time": "2025-01-17T11:48:21.602016Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record_dict)  #横坐标是 steps"
   ],
   "id": "e2554260e338697e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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v5REcAsXLsTEiRMRHc2KZ09x3HzDcfMex8w3HDffcNz8P27aTEdYr76JjY1Vl/rklw3EEyVQ92t0HDffcNy8xzHzDcfNNxw3/41bMMbRq0LXtLQ0mM1mnDp1qs7X5fP27dv7+7ERERFRBPEqKImJicGIESOwaNEix9dqamrU56NHjw7E4yMiIqII4fX0jdSHSGHrOeecg5EjR+Kll15CcXGxYzUOERERUVCCkuuvvx7Z2dl47LHHcPLkSZx11ln4/vvvGxS/EhEREQW80PXee+9VFyIiIqKQtpknIiIi8jcGJURERKQLDEqIiIhIFwLePK0+rau9vzvDSRc6aYEr98tGOZ7juPmG4+Y9jplvOG6+4bj5f9y047YXu9PoPygpLCxUH6XVPBEREYWXwsJCpKSkhH5DPn+QZmsnTpxAUlISTCaT3+5X21Pn6NGjAdsoyIg4br7huHmPY+YbjptvOG7+HzcJFyQg6dixI6KiooyRKZFfpHPnzgG7fxlEPgG9x3HzDcfNexwz33DcfMNx8++4BSpDomGhKxEREekCgxIiIiLSBcMEJbGxsXj88cfVR/Icx803HDfvccx8w3HzDcctPMct6IWuRERERIbOlBAREVF4Y1BCREREusCghIiIiHSBQQkRERHpgmGCktdeew3du3dHXFwcRo0ahXXr1iESzJo1S3XGdb7079/f8f2ysjLcc889aN26NRITE3HNNdfg1KlTde4jIyMDl156KRISEtC2bVs8/PDDqKqqqnObpUuXYvjw4aoiu3fv3njvvfcQTpYvX47LL79cdSKUMfrqq6/qfF/qvR977DF06NAB8fHxmDBhAvbv31/nNjk5ObjxxhtVQ6GWLVvitttuQ1FRUZ3bbNu2DRdeeKF6HkpXxOeee67BY/niiy/U30huM2TIEMybNw/hOm633HJLg+fflClTInrcnnnmGZx77rmqa7W8nqZPn469e/fWuU0wX5fh8t7oybiNGzeuwfPtrrvuiuhxe+ONNzB06FBHs7PRo0dj/vz54ftcsxrAp59+ao2JibG+88471p07d1pvv/12a8uWLa2nTp2yGt3jjz9uHTRokDUzM9Nxyc7Odnz/rrvusnbp0sW6aNEi64YNG6znnXee9fzzz3d8v6qqyjp48GDrhAkTrJs3b7bOmzfPmpaWZp05c6bjNocOHbImJCRYH3zwQeuuXbusr7zyitVsNlu///57a7iQ3+uPf/yj9csvv5TVZta5c+fW+f6zzz5rTUlJsX711VfWrVu3Wq+44gprjx49rKWlpY7bTJkyxTps2DDrmjVrrCtWrLD27t3besMNNzi+n5+fb23Xrp31xhtvtO7YscP6ySefWOPj461vvvmm4zY//fSTGrvnnntOjeWf/vQna3R0tHX79u3WcBy3m2++WY2L8/MvJyenzm0ibdwmT55sfffdd9XvsmXLFuu0adOsXbt2tRYVFQX9dRlO742ejNtFF12kfgfn55s8fyJ53L755hvrd999Z923b59179691j/84Q/qtSHjGI7PNUMEJSNHjrTec889js+rq6utHTt2tD7zzDPWSAhK5A3flby8PPXk/OKLLxxf2717tzq4rF69Wn0uT8CoqCjryZMnHbd54403rMnJydby8nL1+e9//3sV+Di7/vrr1ZtIOKp/cK2pqbG2b9/e+re//a3O2MXGxqoDpJAXovy/9evXO24zf/58q8lksh4/flx9/vrrr1tTU1Md4yYeeeQRa79+/RyfX3fdddZLL720zuMZNWqU9c4777Tqnbug5Morr3T7fzhuVmtWVpYag2XLlgX9dRnO7431x00LSu6//363/4fjZiOvp7fffjssn2thP31TUVGBjRs3qnS78/468vnq1asRCWSaQdLrPXv2VGlyScUJGRfZhtp5bCT93bVrV8fYyEdJhbdr185xm8mTJ6tNmXbu3Om4jfN9aLcxyvimp6fj5MmTdX5H2d9B0o/O4yRTD+ecc47jNnJ7ea6tXbvWcZuxY8ciJiamzjhJCjo3N9ewYylpXUn59uvXD7/5zW9w5swZx/c4bkB+fr762KpVq6C+LsP9vbH+uGk++ugjpKWlYfDgwZg5cyZKSkoc34v0cauursann36K4uJiNY0Tjs+1oG/I52+nT59WfwjnARXy+Z49e2B0cuCUuT05IGRmZmL27Nlqbn7Hjh3qQCtv9HJQqD828j0hH12Nnfa9xm4jT9rS0lJVgxHOtN/T1e/oPAZy4HVmsVjUG6bzbXr06NHgPrTvpaamuh1L7T7CjdSPXH311er3PnjwIP7whz9g6tSp6o3IbDZH/LjJrugPPPAAxowZow6iIlivSwnowvW90dW4iRkzZqBbt27qJEzqkB555BEVvH755ZcRPW7bt29X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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 35
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 测试集",
   "id": "d9f6852bfa6d34fe"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:50:38.653404Z",
     "start_time": "2025-01-17T11:50:38.463001Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()  # 评估模式\n",
    "loss = evaluate(model, test_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}\")"
   ],
   "id": "506c5db54876b6ed",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.4728\n"
     ]
    }
   ],
   "execution_count": 36
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
